package com.nervenets.general;

import com.nervenets.general.config.ApplicationProperties;
import com.nervenets.general.entity.ResponseResult;
import com.nervenets.general.exception.LogicException;
import com.nervenets.general.exception.TokenIllegalException;
import com.nervenets.general.redis.RedisMultiPointLocker;
import com.nervenets.general.service.MailService;
import com.nervenets.general.service.RedisService;
import com.nervenets.general.utils.ClassUtils;
import com.nervenets.general.utils.HttpTools;
import io.swagger.annotations.ApiModelProperty;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.StaleObjectStateException;
import org.springframework.beans.InvalidPropertyException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.InvalidMediaTypeException;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.orm.ObjectOptimisticLockingFailureException;
import org.springframework.transaction.TransactionSystemException;
import org.springframework.util.InvalidMimeTypeException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ValidationException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import static com.nervenets.general.i18n.I18nTranslator.braceTranslate;
import static com.nervenets.general.i18n.I18nTranslator.translate;

@Slf4j
@RestControllerAdvice
public class ApplicationExceptionHandler {
    @Autowired
    private MailService mailService;
    @Autowired
    private Environment environment;
    @Resource
    private RedisService redisService;
    @Resource
    private ApplicationProperties applicationProperties;

    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseEntity<?> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseResult.failed(HttpStatus.BAD_REQUEST.value(), String.format("%s类型错误", e.getPropertyName())));
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        return handleBindingResultError(e.getBindingResult());
    }

    @ExceptionHandler(BindException.class)
    public ResponseEntity<?> handleBindException(BindException e) {
        return handleBindingResultError(e.getBindingResult());
    }

    private ResponseEntity<?> handleBindingResultError(BindingResult bindingResult) {
        FieldError error = bindingResult.getFieldError();
        String field = error.getField();

        try {
            final Object target = bindingResult.getTarget();
            String[] fields = field.split("\\.");

            if (fields.length > 1) {
                List<String> names = new ArrayList<>();
                Class<?> aClass = target.getClass();
                for (String f : fields) {
                    final boolean contains = f.contains("[");
                    final Field declaredField = ClassUtils.getDeclaredField(aClass, contains ? f.substring(0, f.indexOf("[")) : f);
                    ApiModelProperty property = declaredField.getAnnotation(ApiModelProperty.class);
                    if (null != property) {
                        names.add(braceTranslate(property.value()));
                        if (contains) {
                            final int indexOf = Integer.parseInt(f.substring(f.indexOf("[") + 1, f.indexOf("]"))) + 1;
                            names.add(String.format(translate("application.exception.th.message"), indexOf));
                        }
                    }

                    final Type genericType = declaredField.getGenericType();
                    if (genericType instanceof ParameterizedType) {
                        aClass = (Class<?>) ((ParameterizedType) declaredField.getGenericType()).getActualTypeArguments()[0];
                    } else {
                        aClass = (Class<?>) genericType;
                    }
                }
                if (!names.isEmpty())
                    field = org.apache.commons.lang3.StringUtils.joinWith(translate("application.exception.is.message"), names.toArray());
            } else {
                ApiModelProperty property = ClassUtils.getDeclaredField(target.getClass(), fields[0]).getAnnotation(ApiModelProperty.class);

                if (null != property) {
                    field = braceTranslate(property.value());
                }
            }
        } catch (Exception ignored) {

        }

        String defaultMessage = error.getDefaultMessage();
        String message = String.format("%s:%s", field, defaultMessage);
        log.error("handleBindingResultError: {}", error);
        return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseResult.failed(HttpStatus.BAD_REQUEST.value(), message));
    }

    /**
     * 所有异常报错
     *
     * @param request  ..
     * @param response ..
     * @param handler  ..
     * @param ex       产生的异常
     * @return 异常处理返回内容
     */
    @ExceptionHandler(value = {Exception.class, Throwable.class})
    public ResponseEntity<?> allExceptionHandler(HttpServletRequest request, HttpServletResponse response, Object handler, Throwable ex) throws Exception {
        Object keys = request.getAttribute(Global.Constants.SESSION_LOCK_KEYS);
        if (null != keys) ((Vector<String>) keys).forEach(RedisMultiPointLocker::unlock);

        String message = ex.getMessage();
        if (ex instanceof MissingServletRequestParameterException) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseResult.failed(HttpStatus.BAD_REQUEST.value(), translate("application.exception.params.miss.message") + message));
        } else if (ex instanceof HttpMessageNotReadableException || ex instanceof TypeMismatchException) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseResult.failed(HttpStatus.BAD_REQUEST.value(), translate("application.exception.params.error.message") + message));
        } else if (ex instanceof InvalidPropertyException) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseResult.failed(HttpStatus.BAD_REQUEST.value(), translate("application.exception.invalid.property.message") + message));
        } else if (ex instanceof ObjectOptimisticLockingFailureException || ex instanceof StaleObjectStateException || ex instanceof TransactionSystemException) {
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseResult.failed(HttpStatus.BAD_REQUEST.value(), translate("application.exception.data.changed.message")));
        } else if (ex instanceof ServletException || ex instanceof InvalidMediaTypeException) {
            ex.printStackTrace();
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseResult.failed(HttpStatus.BAD_REQUEST.value(), message));
        } else if (ex instanceof IOException) {
            return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE).body(ex.getMessage());
        } else if (ex instanceof ValidationException) {
            if (null != ex.getCause() && ex.getCause() instanceof TokenIllegalException) {
                return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ResponseResult.failed(HttpStatus.UNAUTHORIZED.value(), ex.getCause().getMessage()));
            }
            ex.printStackTrace();
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ResponseResult.failed(HttpStatus.BAD_REQUEST.value(), message));
        } else if (ex instanceof TokenIllegalException) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ResponseResult.failed(HttpStatus.UNAUTHORIZED.value(), message));
        } else if (ex instanceof LogicException) {
            int code = ((LogicException) ex).getCode();
            return ResponseEntity.status(code).body(ResponseResult.failed(code, ex.getMessage(), ((LogicException) ex).getLevel()));
        } else if (ex instanceof InvalidMimeTypeException) {
            return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ResponseResult.failed(HttpStatus.NOT_FOUND.value(), ex.getMessage()));
        } else if (ex instanceof RuntimeException && null != ex.getCause()) {
            return allExceptionHandler(request, response, handler, ex.getCause());
        } else {
            ex.printStackTrace();
            String domain = applicationProperties.getBaseDomain();
            if (!domain.contains("localhost")) {
                if (redisService.canProceedCustom(ex.getClass().getSimpleName(), 60)) {
                    mailService.exceptionEmailSend(environment.getProperty("spring.application.name"), "接口" + request.getRequestURI(), HttpTools.getRequestInfo(request), ex);
                }
            }
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ResponseResult.failed(HttpStatus.INTERNAL_SERVER_ERROR.value(), message));
        }
    }
}
